package com.gitee.srurl.snow.gen;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.gitee.srurl.snow.domain.SnowflakeConstant;
import com.gitee.srurl.snow.domain.SnowflakeData;

/**
 * @author zxw
 * @date 2023/4/22 13:37
 */
public class BorrowFutureTimeSnowflake extends AbstractSnowflake {

    /**
     * 最后一条信息的时间戳
     */
    private long lastTimestamp;

    /**
     * 等待下一毫秒的过程中 运行回拨时钟次数
     * 也就是在等待的过程中 再次回拨时间
     * 允许这么干几次
     */
    private static final int allowWaitingClockBackwardsTimes = 2;

    /**
     * @param snowflakeData 初始化数据
     */
    public BorrowFutureTimeSnowflake(long lastTimestamp, SnowflakeData snowflakeData) {

        super(lastTimestamp, snowflakeData);
        this.lastTimestamp = lastTimestamp + 1;
    }

    @Override
    long doGetNextId() {

        long timestamp = genTime();
        // 正常借用时间去实现 时钟正常的话 目前正常这边没有处理 默认实现那边会处理
        if (timestamp <= this.lastTimestamp) {
            final long sequence = (this.sequence + 1) & SnowflakeConstant.SEQUENCE_MASK;
            if (sequence == 0) {
                int waitTimes = 0;
                while (true) {
                    timestamp = this.tilNextMillis(timestamp);
                    ++waitTimes;
                    // timestamp == -1 说明等待过程中时间被回拨 不为-1 说明正常返回 这边用小于0判断，避免以后出现其他情况设置不同类型负数
                    if (timestamp > 0) {
                        // 设置为下一毫秒
                        this.lastTimestamp = this.lastTimestamp + 1;
                        break;
                    } else if (waitTimes >= allowWaitingClockBackwardsTimes) {
                        // 等到了上限次数 报错
                        throw new IllegalStateException(StrUtil.format("等待下一毫秒过程中，{}次回拨时间，本次ID生成失败！", waitTimes));
                    }
                }
            }
            this.sequence = sequence;
        } else {
            if (randomSequenceLimit > 1) {
                sequence = RandomUtil.randomLong(randomSequenceLimit);
            } else {
                sequence = 0L;
            }
        }

        return buildId(this.lastTimestamp);
    }
}
